package fi.iki.juri.units.generator;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;

import fi.iki.juri.units.generator.ParsedTranslations.Translation;

/**
 * Generates the class fi.iki.juri.units.generated.GeneratedUnits.
 *
 * @author juri
 */
public class Generator {
	public String generate(final Iterable<String> specFiles, final ArrayList<String> transFiles) throws FileNotFoundException {
		final SpecificationParser specParser = new SpecificationParser();
		final TranslationParser transParser = new TranslationParser();
		final Collection<ParsedUnit> units = new ArrayList<ParsedUnit>();
		for (final String file : specFiles) {
			final InputStream stream = new FileInputStream(file);
			units.addAll(specParser.parse(stream));
		}
		final Collection<ParsedTranslations> translations = new ArrayList<ParsedTranslations>();
		for (final String file : transFiles) {
			final InputStream stream = new FileInputStream(file);
			translations.add(transParser.parse(stream));
		}
		final StringWriter klassWriter = new StringWriter();
		writeClassHead(klassWriter);
		final StringBuilder createUnits = new StringBuilder(1024);
		final StringBuilder deriveUnits = new StringBuilder(1024);
		int mapSize = 0;
		for (final ParsedUnit unit : units) {
			createUnits.append(String.format("createUnit(map, \"%s\");\n", unit.getId()));
			mapSize++;
			for (final String alias : unit.getAliases()) {
				createUnits.append(String.format("aliasUnit(map, \"%s\", \"%s\");\n", unit.getId(), alias));
				mapSize++;
			}

			if (unit.getDerive() != null) {
				deriveUnits.append(String.format("map.get(\"%s\").deriveUnit(map.get(\"%s\"), ",  
						unit.getId(), unit.getDerive()));
				if (unit.getFromFormula() != null)
					deriveUnits.append(String.format("\"%s\"", unit.getFromFormula()));
				else
					deriveUnits.append("null");
				if (unit.getToFormula() != null) {
					deriveUnits.append(String.format(", \"%s\"", unit.getToFormula()));
				}
				deriveUnits.append(");\n");
			}
		}
		writeCreateUnitsMapHead(klassWriter, mapSize);
		klassWriter.write(createUnits.toString());
		klassWriter.write(deriveUnits.toString());
		writeCreateUnitsMapTail(klassWriter);
		writeGetTranslationsHead(klassWriter);
		for (final ParsedTranslations trans : translations) {
			klassWriter.write(String.format("trans = new HashMap<String,Unit>();\n"));
			klassWriter.write(String.format("translations.put(\"%s\", trans);\n", trans.getLocale()));
			for (final Translation t : trans.getTranslations()) {
				klassWriter.write(String.format("trans.put(\"%s\", units.get(\"%s\"));\n", t.getTranslation(), t.getUnit()));
			}
		}
		writeGetTranslationsTail(klassWriter);
		writeClassTail(klassWriter);
		return klassWriter.toString();
	}

	private void writeClassTail(final StringWriter klassWriter) {
		final String tail =	"}\n";
		klassWriter.write(tail);
	}

	private void writeClassHead(final StringWriter klassWriter) {
		final String head = "package fi.iki.juri.units;\n" + 
				"\n" + 
				"import java.util.HashMap;\n" + 
				"import java.util.Map;\n" + 
				"\n" + 
				"import fi.iki.juri.units.Unit;\n" +
				"\n" +
				"/**\n" + 
				" * Initialize the recognized units.\n" + 
				" *\n" + 
				" * This class has been generated by fi.iki.juri.units.generator.Generator. Do not edit.\n" + 
				" */\n" + 
				"public class GeneratedUnits {\n" + 
				"	private static void createUnit(final HashMap<String,Unit> map, final String name) {\n" + 
				"		map.put(name, new Unit(name));\n" + 
				"	}\n" + 
				"	\n" + 
				"	private static void aliasUnit(final HashMap<String,Unit> map, final String original, final String alias) {\n" + 
				"		map.put(alias, map.get(original));\n" + 
				"	}\n" + 
				"	\n";
		klassWriter.write(head);
	}
	
	private void writeCreateUnitsMapHead(final StringWriter klassWriter, int mapSize) {
		// According to the HashMap documentation, higher load factor increases
		// lookup cost. Using a perfect hash generator here would be a good idea.
		final String methodHead =
			"	public static Map<String, Unit> createUnitsMap() {\n" + 
			"		final HashMap<String, Unit> map = new HashMap<String, Unit>(%d, 0.75F);\n";
		klassWriter.write(String.format(methodHead, (int)(mapSize/0.75)+1));
	}

	private void writeCreateUnitsMapTail(final StringWriter klassWriter) {
		final String methodTail =
			"		return map;\n" + 
			"	}\n";
		klassWriter.write(methodTail);
	}
	
	private void writeGetTranslationsHead(final StringWriter klassWriter) {
		final String methodHead =
			"	public static Map<String,? extends Map<String,Unit>> getTranslations(final Map<String,Unit> units) {\n" +
			"   	final HashMap<String,HashMap<String,Unit>> translations = new HashMap<String,HashMap<String,Unit>>();\n" +
			"		HashMap<String,Unit> trans;\n";
			klassWriter.write(methodHead);
	}
	
	private void writeGetTranslationsTail(final StringWriter klassWriter) {
		final String methodTail =
			"		return translations;\n" +
			"	}\n";
		klassWriter.write(methodTail);
	}
	
	public static void main(final String[] args) {
		if (args.length != 3) {
			System.err.println("The JUGC generator takes three arguments:");
			System.err.println("<specification files> <translation files> <target directory>");
			System.err.println();
			System.err.println("The first two arguments are lists of files, separated by the ");
			System.err.println("CLASSPATH separator.");
			System.err.println("Specification files are the files containing unit specifications.");
			System.err.println("Translation files are the files containing translated unit names.");
			System.err.println();
			System.err.println("Target directory specifies the directory where to output the generated");
			System.err.println("Java files.");
			System.exit(1);
		}
		final Generator g = new Generator();
		final ArrayList<String> specFiles = new ArrayList<String>();
		for (final String file : args[0].split(System.getProperty("path.separator"))) {
			specFiles.add(file);
		}
		final ArrayList<String> transFiles = new ArrayList<String>();
		for (final String file : args[1].split(System.getProperty("path.separator"))) {
			transFiles.add(file);
		}
		try {
			final String klass = g.generate(specFiles, transFiles);
			final FileWriter writer = new FileWriter(new File(args[2], "GeneratedUnits.java"));
			writer.write(klass);
			writer.close();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
